﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CarbonExchange.Bll
{
    public class ContentNodeFactory : FactorySequenceBase<ContentNode>
    {
        public ContentNodeFactory() : base() { }

        public ContentNodeFactory(CarbonExchange.AmeeQuery.IAmeeCommand ameeCommand) : base(ameeCommand) { }

        public ContentNodeFactory(CarbonExchange.Dal.Context dalContext) : base(dalContext) { }

        public ContentNodeFactory(CarbonExchange.AmeeQuery.IAmeeCommand ameeCommand, CarbonExchange.Dal.Context dalContext) : base(ameeCommand, dalContext) { }

        public ContentNode CreateItem(int siteId)
        {
            var tmp = new ContentNode();
            tmp.SiteId = siteId;
            tmp.LiveVersion = ContentNodeConverter.GetLiveVersion(DalContext);
            return tmp;
        }

        protected override void SetDalSequence(ContentNode item, int sequence)
        {
            throw new NotImplementedException();
        }

        protected override void SetInitalSequence(IQueryable<ContentNode> items)
        {
            var itemsToSet = items.Where(x => x.Id == 0);
            if (itemsToSet.Count() > 0)
            {
                var groups = items.GroupBy(x => x.ParentGuid);
                foreach (var group in groups)
                {
                    var i = 0;
                    foreach (var item in group)
                    {
                        item.Sequence = i;
                        i++;
                    }
                }
            }
        }

        protected override void MarkItemForDeletion(ContentNode item)
        {
            throw new NotImplementedException();
        }

        protected override void Validate(IQueryable<ContentNode> items, CarbonExchange.Bll.Exceptions.ValidationException exp)
        {
            items.Validate(exp, DalContext);
        }

        protected override void PopulateDataChanges(ContentNode item, object dataItem)
        {
            var tmp = dataItem as Dal.ContentNode;
            if (tmp != null)
            {
                item.Id = tmp.Id;
                item.CID = tmp.CID;
            }
        }

        protected override object GetDataItemForUpdate(ContentNode item)
        {
            return item.Convert(DalContext);
        }

        internal class JoinHelper
        {
            internal Dal.ContentNode ContentNode { get; set; }
            internal Dal.ContentNodePosition Position { get; set; }
            internal Dal.Site Site { get; set; }
            internal Dal.Template Template { get; set; }
        }

        private IQueryable<JoinHelper> GetSelection()//DateTime? siteVersion, int siteId)
        {
            return DalContext.ContentNodes.Where(x => !x.Deleted)
                .Join(
                    DalContext.ContentNodePositions
                    , x => x.Id
                    , y => y.ContentNodeId
                    , (x, y) => new JoinHelper() { ContentNode = x, Position = y, Site = x.Site, Template = x.Template }
                    ).OrderByDescending(x => x.Position.PublishDate);
       }

        private JoinHelper GetVersion(IQueryable<JoinHelper> selection, DateTime? version)
        {
            if (version != null)
            {
                return selection.Where(x => x.Position.PublishDate <= version.Value.ToUniversalTime()).OrderByDescending(x => x.Position.PublishDate).FirstOrDefault();
            }
            else
            {
                return selection.Where(x => x.Position.PublishDate == null).OrderByDescending(x => x.Position.PublishDate).FirstOrDefault();
            }
        }

        public ContentNode GetById(int id)
        {
            return GetSelection().Where(x => x.ContentNode.Id == id).OrderBy(x => x.Position.PublishDate).FirstOrDefault().Convert(DalContext);
        }

        public ContentNode GetById(int id, DateTime? version)
        {
            var tmp = GetSelection().Where(x => x.ContentNode.Id == id);
            return GetVersion(tmp, version).Convert(DalContext);
        }

        public ContentNode GetByNodeGuid(Guid nodeGuid, DateTime? version)
        {
            var tmp = GetSelection().Where(x => x.ContentNode.NodeGuid == nodeGuid);
            return GetVersion(tmp, version).Convert(DalContext);
        }

        public ContentNode GetBySiteIdAndPath(int siteId, string path, DateTime? version)
        {
            if(path.EndsWith("/"))
            {
                path = path.Substring(0, path.Length - 1);
            }

            var tmp = GetSelection().Where(x => x.Site.Id == siteId);
            tmp = tmp.Where(x=> ((x.Position.ParentPath ?? "") + x.ContentNode.Name) == path);
            return GetVersion(tmp, version).Convert(DalContext);
        }

        public ContentNode GetBySiteUrlAndPath(string siteUrl, string path, DateTime? version)
        {
            if (path.EndsWith("/"))
            {
                path = path.Substring(0, path.Length - 1);
            }

            var tmp = GetSelection().Join(DalContext.SiteRoots.Where(y => y.Url == siteUrl), x => x.Site.Id, y => y.SiteId, (x, y) => x);
            tmp = tmp.Where(x => ((x.Position.ParentPath ?? "") + x.ContentNode.Name) == path);
            return GetVersion(tmp, version).Convert(DalContext);
        }

        public IQueryable<ContentNode> GetAllByParentGuid(Guid parentGuid, DateTime? version)
        {
            var tmp = GetSelection().Where(x => x.Position.ParentNodeGuid == parentGuid);
            if (!version.HasValue)
            {
                tmp = tmp.Where(x => x.Position.PublishDate == null);
            }
            else
            {
                var grouping = tmp.Where(x => x.Position.PublishDate != null && x.Position.PublishDate <= version.Value).GroupBy(x => x.Position.PublishDate.Value).OrderByDescending(x => x.Key).FirstOrDefault();
                if (grouping != null)
                {
                    tmp = grouping.AsQueryable();
                }
                else
                {
                    tmp = new JoinHelper[0].AsQueryable();
                }
            }
            return tmp.Convert(DalContext);
        }

        public IQueryable<ContentNode> GetAllBySiteIdAndParentPath(int siteId, string parentPath, DateTime? version)
        {
            if (!parentPath.EndsWith("/"))
            {
                parentPath = string.Concat(parentPath, "/");
            }
            var tmp = GetSelection().Where(x => x.Site.Id == siteId);
            tmp = tmp.Where(x => x.Position.ParentPath == parentPath);
            if (version == null)
            {
                tmp = tmp.Where(x => x.Position.PublishDate == null);
            }
            else
            {
                var allChildVersions = tmp.Where(x => (x.Position.PublishDate ?? DateTime.MaxValue) <= version.Value);
                var grouped = allChildVersions.GroupBy(x => x.Position.PublishDate.Value).OrderByDescending(x => x.Key).FirstOrDefault();
                if (grouped != null)
                {
                    tmp = grouped.AsQueryable();
                }
                else
                {
                    tmp = new JoinHelper[0].AsQueryable();
                }
            }
            return tmp.Convert(DalContext);
        }


        public ContentNode GetTopLevelBySiteId(int siteId, DateTime? version)
        {
            var tmp = GetSelection().Where(x => x.Site.Id == siteId).Where(x => x.Position.ParentNodeGuid == null);
            return GetVersion(tmp, version).Convert(DalContext);
        }

        public IQueryable<ContentNode> GetCurrentVersions(Guid nodeGuid)
        {
            //dont get positions changes as we are only getting content versions ie join published from to publish date
            IQueryable<JoinHelper> pending = GetSelection().Where(x => x.ContentNode.NodeGuid == nodeGuid && x.Position.PublishDate == null).Take(1);
            IQueryable<JoinHelper> future = GetSelection().Where(x => x.ContentNode.NodeGuid == nodeGuid && x.ContentNode.PublishFrom != null && x.ContentNode.PublishFrom > DateTime.UtcNow && x.Position.PublishDate == x.ContentNode.PublishFrom).OrderByDescending(x => x.ContentNode.PublishFrom);
            IQueryable<JoinHelper> current = GetSelection().Where(x => x.ContentNode.NodeGuid == nodeGuid && x.ContentNode.PublishFrom != null && x.ContentNode.PublishFrom <= DateTime.UtcNow && x.Position.PublishDate == x.ContentNode.PublishFrom).OrderByDescending(x => x.ContentNode.PublishFrom).Take(1);
            var union = pending.Union(future).Union(current);
            return union.Convert(DalContext);
        }

        public IQueryable<ContentNode> GetAllVersions(Guid nodeGuid)
        {
            //dont get positions changes as we are only getting content versions ie join published from to publish date
            IQueryable<JoinHelper> pending = GetSelection().Where(x => x.ContentNode.NodeGuid == nodeGuid && x.Position.PublishDate == null).Take(1);
            IQueryable<JoinHelper> published = GetSelection().Where(x => x.ContentNode.NodeGuid == nodeGuid && x.ContentNode.PublishFrom != null && x.Position.PublishDate == x.ContentNode.PublishFrom).OrderByDescending(x => x.ContentNode.PublishFrom);
            var union = pending.Union(published);
            return union.OrderByDescending(x => (x.ContentNode.PublishFrom ?? DateTime.MaxValue)).Convert(DalContext);
        }

        public override void Delete(IQueryable<ContentNode> items)
        {
            //only post that are still pending and have no current versions can be deleted
            var error = false;
            if (items.Where(x => x.PublishFrom != null).Count() > 0)
            {
                error = true;
            }
            foreach (var item in items)
            {
                var otherVersions = DalContext.ContentNodes.Where(x => !x.Deleted && (x.PublishTo ?? DateTime.MaxValue) > DateTime.Now && items.Select(y => y.NodeGuid).Contains(x.NodeGuid)).Count();

                if (otherVersions > 0)
                {
                    error = true;
                }
            }
            if (error)
            {
                throw new Bll.Exceptions.ValidationException("Only pending posts with no current versions can be deleted");
            }
            base.Delete(items);
        }

        public void Retire(ContentNode tmp, bool allVersions)
        {
            tmp.PublishTo = DateTime.Now;
            if (allVersions)
            {
                List<ContentNode> nodes = new List<ContentNode>();
                nodes.Add(tmp);
                nodes.AddRange(GetCurrentVersions(tmp.NodeGuid).Where(x => x.Id != tmp.Id));
                foreach (ContentNode node in nodes.Where(x => x != tmp))
                {
                    node.PublishTo = nodes[0].PublishTo;
                }
                Save(nodes);
            }
            else
            {
                Save(tmp);
            }
        }

        public void Publish(ContentNode node)
        {
            Publish(node, false, false);

        }

        public void Publish(ContentNode node, bool includeChidren, bool includeOnlyMarked)
        {
            Publish(node, includeChidren, includeOnlyMarked, DateTime.Now, null);
        }

        public void Publish(ContentNode node, bool includeChidren, bool includeOnlyMarked, DateTime from, DateTime? to)
        {
            //only publish pending versions
            if (node.PublishFrom != null)
            {
                throw new Exceptions.ValidationException("ContentNode node must be pending in order to publsh");
            }

            if (includeChidren || includeOnlyMarked)
            {
                throw new NotImplementedException();
            }

            node.PublishFrom = from;
            node.PublishTo = to;
            node.RequiresPublishing = false;

            Save(node);
        }
    }

    internal static class ContentNodeConverter
    {
        internal static ContentNode Convert(this ContentNodeFactory.JoinHelper source, Dal.Context context)
        {
            return ConvertItem(source, context);
        }

        internal static IQueryable<ContentNode> Convert(this IQueryable<ContentNodeFactory.JoinHelper> source, Dal.Context context)
        {
            return source.Select(x => ConvertItem(x, context));
        }

        private static ContentNode ConvertItem(ContentNodeFactory.JoinHelper item, Dal.Context context)
        {
            if (item != null)
            {
                return new ContentNode()
                {
                    CID = item.ContentNode.CID,
                    CreatedDateTime = item.ContentNode.CreatedDateTime.ToServerTime(),
                    DeveloperLocked = item.ContentNode.DeveloperLocked,
                    DisplayName = item.ContentNode.DisplayName,
                    Id = item.ContentNode.Id,
                    Name = item.ContentNode.Name,
                    NodeGuid = item.ContentNode.NodeGuid,
                    Notes = item.ContentNode.Notes,
                    PublishFrom = item.ContentNode.PublishFrom.ToServerTime(),
                    PublishTo = item.ContentNode.PublishTo.ToServerTime(),
                    ReadyToPublish = item.ContentNode.ReadyToPublish,
                    RequiresPublishing = item.ContentNode.RequiresPublishing,
                    SiteId = item.ContentNode.SiteId,
                    Summary = item.ContentNode.Summary,
                    TemplateId = item.ContentNode.TemplateId,
                    SiteName = item.Site.Name,
                    TemplateName = item.Template.Name,
                    CurrentVersion = item.Position.PublishDate.ToServerTime(),
                    ParentGuid = item.Position.ParentNodeGuid,
                    ParentPath = item.Position.ParentPath,
                    Sequence = item.Position.Sequence,
                    ParentSequence = item.Position.ParentSequence,
                    Context = context,
                    TemplateFiles = new CarbonExchange.LinqUtilities.LazyList<CarbonExchange.Dal.TemplateFile>(
                        context.TemplateFiles.Where(x => !x.Deleted && x.TemplateId == item.Template.Id).OrderBy(x => x.Name)
                        ),
                    LiveVersion = GetLiveVersion(context)

                };
                
            }
            else
            {
                return null;
            }
        }

        public static CarbonExchange.LinqUtilities.LazyItem<DateTime, CarbonExchange.Dal.ContentNodePosition> GetLiveVersion(Dal.Context context)
        {
            return new CarbonExchange.LinqUtilities.LazyItem<DateTime, CarbonExchange.Dal.ContentNodePosition>(
                context.ContentNodePositions.Where(x => (x.PublishDate ?? DateTime.MaxValue) <= DateTime.UtcNow).OrderByDescending(x => x.PublishDate).Take(1)
                , new CarbonExchange.LinqUtilities.LazyItem<DateTime, CarbonExchange.Dal.ContentNodePosition>.Convertion(x => x.PublishDate.Value.ToServerTime())
            );
        }
        
        internal static IQueryable<Dal.ContentNode> Convert(this IQueryable<ContentNode> source, Dal.Context context)
        {
            return source.Select(x => ConvertItem(x, context));
        }

        internal static Dal.ContentNode Convert(this ContentNode source, Dal.Context context)
        {
            return ConvertItem(source, context);
        }

        private static void SetParentInfo(ContentNode item, Dal.Context context)
        {
            if (item.ParentGuid == null)
            {
                item.ParentPath = null;
                item.ParentSequence = null;
            }
            else
            {
                var positionInfo = context.ContentNodes.Where(x => x.NodeGuid == item.ParentGuid && !x.Deleted && (x.PublishFrom ?? DateTime.MaxValue) <= (item.CurrentVersion.ToDBTime() ?? DateTime.MaxValue) && (x.PublishTo ?? DateTime.MaxValue) >= (item.CurrentVersion.ToDBTime() ?? DateTime.MaxValue))
                    .Join(context.ContentNodePositions.Where(y => (y.PublishDate ?? DateTime.MaxValue) == (item.CurrentVersion ?? DateTime.MaxValue)), x => x.Id, y => y.ContentNodeId, (x, y) => new { ParentPath = (y.ParentPath ?? "") + x.Name + "/", ParentSequence = (y.ParentSequence ?? "") + y.Sequence.ToString() })
                    .Single();
                item.ParentPath = positionInfo.ParentPath;
                item.ParentSequence = positionInfo.ParentSequence;
            }
        }

        private static Dal.ContentNode ConvertItem(ContentNode item, Dal.Context context)
        {
            if (item != null)
            {
                Dal.ContentNode tmpNode = null;
                Dal.ContentNodePosition tmpPosition = null;


                //fix home page name and sequence
                if (item.ParentGuid == null)
                {
                    item.Sequence = 0;
                    item.Name = "~";
                }

                if (item.Id != 0)
                {
                    tmpNode = context.ContentNodes.Where(x => !x.Deleted && x.Id == item.Id).Single();
                    tmpPosition = context.ContentNodePositions.Where(x => x.ContentNodeId == item.Id && (x.PublishDate ?? DateTime.MaxValue) == (item.CurrentVersion.ToDBTime() ?? DateTime.MaxValue)).Single();

                    //if parent has changed recaluate parent path and parent sequence
                    if(tmpPosition.ParentNodeGuid != item.ParentGuid)
                    {
                        throw new NotImplementedException();
                        SetParentInfo(item, context);
                    }

                    //if parentPath or parent sequence has changed update children
                    if (item.Name != tmpNode.Name || tmpPosition.ParentNodeGuid != item.ParentGuid || tmpPosition.Sequence != item.Sequence)
                    {
                        throw new NotImplementedException();

                        string newParentPath = string.Concat(item.ParentPath, item.Name, "/");
                        string oldParentPath = string.Concat(tmpPosition.ParentPath, tmpNode.Name, "/");
                        string newParentSequence = string.Concat(item.ParentSequence, item.Sequence);
                        string oldParentSequence = string.Concat(tmpPosition.ParentSequence, tmpPosition.Sequence);
                        var children = context.ContentNodePositions.Where(x => x.PublishDate == item.CurrentVersion.ToDBTime() && x.ParentPath.StartsWith(oldParentPath));
                        foreach (var child in children)
                        {
                            child.ParentPath = string.Concat(newParentPath, child.ParentPath.Substring(oldParentPath.Length));
                            child.ParentSequence = string.Concat(newParentSequence, child.ParentSequence.Substring(oldParentSequence.Length));
                            child.ContentNode.RequiresPublishing = true;
                        }
                    }

                    if (tmpNode.PublishFrom == null && item.PublishFrom != null)
                    {
                        item.CurrentVersion = item.PublishFrom;
                        tmpNode.PublishFrom = item.PublishFrom.ToDBTime();
                        //delete the tmpPosition as a new one will be created anyway
                        context.ContentNodePositions.DeleteOnSubmit(tmpPosition);

                        //check the parent properties incase parent node now publshed is different
                        //SetParentInfo(item, context);

                        //create new pending version
                        var  tmpPendingNode = new Dal.ContentNode();
                        tmpPendingNode.CreatedDateTime = item.PublishFrom.Value;
                        tmpPendingNode.DeveloperLocked = tmpNode.DeveloperLocked;
                        tmpPendingNode.DisplayName = tmpNode.DisplayName;
                        tmpPendingNode.Name = tmpNode.Name;
                        tmpPendingNode.NodeGuid = tmpNode.NodeGuid;
                        tmpPendingNode.Notes = tmpNode.Notes;
                        tmpPendingNode.SiteId = tmpNode.SiteId;
                        tmpPendingNode.Summary = tmpNode.Summary;
                        tmpPendingNode.TemplateId = tmpNode.TemplateId;

                        tmpPendingNode.ContentNodePositions.Add(new CarbonExchange.Dal.ContentNodePosition());
                        tmpPendingNode.ContentNodePositions.Last().Id = Guid.NewGuid();
                        tmpPendingNode.ContentNodePositions.Last().ParentNodeGuid = tmpPosition.ParentNodeGuid;
                        tmpPendingNode.ContentNodePositions.Last().ParentPath = tmpPosition.ParentPath;
                        tmpPendingNode.ContentNodePositions.Last().ParentSequence = tmpPosition.ParentSequence;
                        tmpPendingNode.ContentNodePositions.Last().Sequence = tmpPosition.Sequence;

                        context.ContentNodes.InsertOnSubmit(tmpPendingNode);

                        //close any existing vartions
                        CloseOverlappingVersion(context, item.NodeGuid, item.PublishFrom.Value, item.PublishTo);
                        BuidTree(context, item.SiteId, item.PublishFrom.Value);
                    }
                }
                else
                {
                    //set up all created values
                    if (item.NodeGuid == Guid.Empty)
                    {
                        item.NodeGuid = Guid.NewGuid();
                    }
                    if (item.ParentGuid != null)
                    {
                        SetParentInfo(item, context);
                        var maxSequence = context.ContentNodePositions.Where(x => x.ParentNodeGuid == item.ParentGuid && x.PublishDate == null).Select(x => (int?)x.Sequence).Max();
                        item.Sequence = item.Sequence + (maxSequence ?? -1) + 1;
                    }

                    item.CreatedDateTime = DateTime.Now;
                    item.RequiresPublishing = true;
                    item.TemplateName = context.Templates.Where(x => x.Id == item.TemplateId && !x.Deleted).Select(x => x.Name).SingleOrDefault();
                    item.SiteName = context.Sites.Where(x => x.Id == item.SiteId && !x.Deleted).Select(x => x.Name).SingleOrDefault();


                    tmpNode = new CarbonExchange.Dal.ContentNode();
                    context.ContentNodes.InsertOnSubmit(tmpNode);

                    tmpNode.NodeGuid = item.NodeGuid;
                    tmpNode.SiteId = item.SiteId;
                    tmpNode.CreatedDateTime = item.CreatedDateTime.ToDBTime();

                    tmpPosition = new CarbonExchange.Dal.ContentNodePosition();
                    tmpNode.ContentNodePositions.Add(tmpPosition);
                    
                    tmpPosition.Id = Guid.NewGuid();
                    tmpPosition.ParentNodeGuid = item.ParentGuid;
                    tmpPosition.ParentPath = item.ParentPath;
                    tmpPosition.ParentSequence = item.ParentSequence;
                    tmpPosition.Sequence = item.Sequence;


                }

                tmpNode.DeveloperLocked = item.DeveloperLocked;
                tmpNode.DisplayName = item.DisplayName;
                tmpNode.Name = item.Name;
                tmpNode.Notes = item.Notes;
                tmpNode.ReadyToPublish = item.ReadyToPublish;
                tmpNode.Summary = item.Summary;
                tmpNode.TemplateId = item.TemplateId;

                if (item.PublishFrom == null && item.RequiresPublishing)
                {
                    tmpNode.RequiresPublishing = true;
                }
                else if (item.PublishFrom != null)
                {
                    tmpNode.RequiresPublishing = false;
                }
                

/*                tmpPosition.ParentNodeGuid = item.ParentGuid;
                tmpPosition.ParentPath = item.ParentPath;
                tmpPosition.ParentSequence = item.ParentSequence;
                tmpPosition.Sequence = item.Sequence;
                */
                /*
                if(item.Id > 0)
                {

                    
                    if (item.PublishFrom == null)
                    {
                        context.ContentNodePositions.DeleteAllOnSubmit(context.ContentNodePositions.Where(x => x.ContentNodeId == item.Id && x.PublishDate != null));
                    }
                    else
                    {
                        //update any versions which would overlap there should only be one!
                        var overlapping = context.ContentNodes.Where(x => x.NodeGuid == item.NodeGuid && x.Id != item.Id && (x.PublishTo ?? DateTime.MaxValue) > item.PublishFrom.Value.ToDBTime());
                        foreach(var i in overlapping)
                        {
                            i.PublishTo = item.PublishFrom.ToDBTime();
                            var outside = context.ContentNodePositions.Where(x => x.ContentNodeId == i.Id && x.PublishDate.Value >= i.PublishTo.Value);
                            context.ContentNodePositions.DeleteAllOnSubmit(outside);
                        }

                        //now remove all positions that are outside the range
                        var beforeRange = context.ContentNodePositions.Where(x => x.ContentNodeId == item.Id && (x.PublishDate ?? DateTime.MaxValue) < item.PublishFrom.Value.ToDBTime());
                        var afterRange = context.ContentNodePositions.Where(x => x.ContentNodeId == item.Id && (x.PublishDate ?? DateTime.MaxValue) >= (item.PublishTo.ToDBTime() ?? DateTime.MaxValue));
                        context.ContentNodePositions.DeleteAllOnSubmit(beforeRange);
                        context.ContentNodePositions.DeleteAllOnSubmit(afterRange);

                        //if there isnt a position record for any node at the start date time we are building a new tree.

                        //otherwise check there is a position record for the start datetime specified

                        // create a new pending version if there isnt one
                        if (context.ContentNodes.Where(x => x.NodeGuid == item.NodeGuid && x.PublishFrom == null).Count() == 0)
                        {
                            Dal.ContentNode newPendingNode = new CarbonExchange.Dal.ContentNode();
                            newPendingNode.CreatedDateTime = DateTime.Now.ToDBTime();
                            newPendingNode.DeveloperLocked = item.DeveloperLocked;
                            newPendingNode.DisplayName = item.DisplayName;
                            newPendingNode.Name = item.Name;
                            newPendingNode.NodeGuid = item.NodeGuid;
                            newPendingNode.Notes = item.Notes;
                            newPendingNode.SiteId = item.SiteId;
                            newPendingNode.Summary = item.Summary;
                            newPendingNode.TemplateId = item.TemplateId;

                            newPendingNode.ContentNodePositions.Add(new CarbonExchange.Dal.ContentNodePosition());
                            newPendingNode.ContentNodePositions.Last().ParentNodeGuid = item.ParentGuid;
                            newPendingNode.ContentNodePositions.Last().ParentPath = item.p
                        }
                    }
                }*/
                return tmpNode;
            }
            else
            {
                return null;
            }
        }

        private static void CloseOverlappingVersion(Dal.Context context, Guid nodeGuid, DateTime publishFrom, DateTime? publishTo)
        {
            var tmpDateFrom = publishFrom.ToDBTime();
            var tmpDateTo = publishTo.ToDBTime();
            var previous = context.ContentNodes.Where(x => x.NodeGuid == nodeGuid && (x.PublishFrom ?? DateTime.MaxValue) <= tmpDateFrom && (x.PublishTo ?? DateTime.MaxValue) > tmpDateFrom);
            foreach(var item in previous)
            {
                item.PublishTo = publishFrom;
            }
            var after = context.ContentNodes.Where(x => x.NodeGuid == nodeGuid && (x.PublishFrom ?? DateTime.MaxValue) <= tmpDateTo && (x.PublishTo ?? DateTime.MaxValue) > tmpDateTo);
            foreach (var item in previous)
            {
                item.PublishFrom = publishTo;
            }
            //clean up any position records outside range
            var tmp2 = context.ContentNodePositions.Where(x => x.ContentNode.NodeGuid == nodeGuid && (x.ContentNode.PublishFrom ?? DateTime.MaxValue) > (x.PublishDate ?? DateTime.MaxValue))
                .Union(
                    context.ContentNodePositions.Where(x => x.ContentNode.NodeGuid == nodeGuid && (x.ContentNode.PublishTo ?? DateTime.MaxValue) <= (x.PublishDate ?? DateTime.MaxValue))
                );
            context.ContentNodePositions.DeleteAllOnSubmit(tmp2);
        }

        private static void BuidTree(Dal.Context context, int siteId, DateTime publishDate)
        {
            var tmpDate = publishDate.ToDBTime();
            //check home page has a version so dont need to do outer join bellow unless it is the home page we are creating the record for
            var hasHomePageVersion = context.ContentNodePositions.Where(x => x.ContentNode.SiteId == siteId && !x.ContentNode.Deleted && x.PublishDate == tmpDate && x.ParentNodeGuid == null).Count() == 1;
            if (!hasHomePageVersion)
            {
                var parentId = context.ContentNodePositions.Where(x=> x.ContentNode.SiteId == siteId && !x.ContentNode.Deleted && x.ParentNodeGuid == null).OrderByDescending(x=> (x.PublishDate ?? DateTime.MaxValue)).Take(1).Select(x => x.ContentNodeId).SingleOrDefault();
                if (parentId != 0)
                {
                    Dal.ContentNodePosition tmpHomePagePostion = new CarbonExchange.Dal.ContentNodePosition();
                    tmpHomePagePostion.Id = Guid.NewGuid();
                    tmpHomePagePostion.ContentNodeId = parentId;
                    tmpHomePagePostion.PublishDate = tmpDate;
                    tmpHomePagePostion.Sequence = 0;
                    context.ContentNodePositions.InsertOnSubmit(tmpHomePagePostion);
                }
            }

            //get all posts where there is a position record missing
            var tmpNodes = context.ContentNodes.Where(x => x.SiteId == siteId && (x.PublishFrom ?? DateTime.MaxValue) <= tmpDate && (x.PublishTo ?? DateTime.MaxValue) > tmpDate);

            //required nodes and there previos position record
            var nodesNotExisting = context.ContentNodes.Where(x => x.SiteId == siteId && (x.PublishFrom ?? DateTime.MaxValue) <= tmpDate && (x.PublishTo ?? DateTime.MaxValue) > tmpDate)
                .Join(context.ContentNodePositions.Where(y => (y.PublishDate ?? DateTime.MaxValue) <= tmpDate).OrderByDescending(y => y.PublishDate), x => x.Id, y => y.ContentNodeId, (x, y) => new { Node = x, Position = y })
                .GroupBy(x => x.Node.Id).Select(x=>x.First()).Where(x => x.Position.PublishDate != publishDate);

            //all potential parents
            var parents = context.ContentNodes.Where(x => x.SiteId == siteId && !x.Deleted)
                .Join(context.ContentNodePositions.Where(y => (y.PublishDate ?? DateTime.MaxValue) <= tmpDate), x => x.Id, y => y.ContentNodeId, (x, y) => new { Node = x, Position = y }).GroupBy(x => x.Node.Id).Select(x => x.First());
            //required children joined to parents
            var joined = nodesNotExisting.Join(parents, x => x.Position.ParentNodeGuid, y => y.Node.NodeGuid, (x, y) => new { Child = x, Parent = y });
            //new position records
            var newPositions = joined.ToArray().Select(x => 
                    new Dal.ContentNodePosition() 
                    { 
                        ContentNodeId = x.Child.Node.Id,
                        Id = Guid.NewGuid(),
                        ParentNodeGuid = x.Parent.Node.NodeGuid,
                        ParentPath = string.Concat(x.Parent.Position.ParentPath,  x.Parent.Node.Name, "/"),
                        ParentSequence = string.Concat(x.Parent.Position.ParentSequence, x.Parent.Position.Sequence),
                        PublishDate = tmpDate,
                        Sequence = x.Child.Position.Sequence
                    }
                    );
            var tmp = newPositions.ToArray();

            context.ContentNodePositions.InsertAllOnSubmit(newPositions);
        }



        internal static void Validate(this IQueryable<ContentNode> source, Exceptions.ValidationException e, Dal.Context context)
        {
            foreach (var item in source)
            {
                item.Validate(e, context);
            }
        }

        internal static void Validate(this ContentNode source, Exceptions.ValidationException e, Dal.Context context)
        {
            if (source.ParentGuid != null)
            {
                if (string.IsNullOrEmpty(source.Name))
                {
                    e.Add("Content Node Name must be set");
                }
            }
            else
            {
                if (context.ContentNodes.Where(x => x.NodeGuid != source.NodeGuid && x.SiteId == source.SiteId)
                    .Join(context.ContentNodePositions.Where(y => y.ParentNodeGuid == null), x => x.Id, y => y.ContentNodeId, (x, y) => x.NodeGuid).Count() != 0)
                {
                    e.Add("Site Already has a Home Page Node");
                }
            }

            if (string.IsNullOrEmpty(source.DisplayName))
            {
                e.Add("Content Node Display Name must be set");
            }
        }

    }
}
